package com.ikingtech.platform.service.tenant.controller;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ikingtech.framework.sdk.base.model.PageResult;
import com.ikingtech.framework.sdk.context.event.SystemInitEvent;
import com.ikingtech.framework.sdk.context.event.TenantDeleteEvent;
import com.ikingtech.framework.sdk.context.event.TenantInitEvent;
import com.ikingtech.framework.sdk.context.event.application.ApplicationPublishEvent;
import com.ikingtech.framework.sdk.context.event.application.DevApplicationMenuInitEvent;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.enums.system.tenant.TenantStatusEnum;
import com.ikingtech.framework.sdk.enums.system.tenant.TenantTypeEnum;
import com.ikingtech.framework.sdk.log.embedded.annotation.OperationLog;
import com.ikingtech.framework.sdk.tenant.api.TenantApi;
import com.ikingtech.framework.sdk.tenant.api.TenantUserApi;
import com.ikingtech.framework.sdk.tenant.model.TenantDTO;
import com.ikingtech.framework.sdk.tenant.model.TenantQueryParamDTO;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.tenant.entity.TenantDO;
import com.ikingtech.platform.service.tenant.exception.TenantExceptionInfo;
import com.ikingtech.platform.service.tenant.service.TenantRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

import static com.ikingtech.framework.sdk.context.constant.SecurityConstants.DEFAULT_TENANT_CODE;
import static com.ikingtech.framework.sdk.context.constant.SecurityConstants.DEFAULT_TENANT_NAME;

/**
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/tenant", name = "系统管理-租户管理", description = "系统管理-租户管理")
public class TenantController implements TenantApi {

    private final TenantRepository repo;

    private final TenantUserApi tenantUserApi;

    private final ApplicationContext applicationContext;

    /**
     * 添加租户
     *
     * @param tenant 租户信息
     * @return 返回添加结果
     */
    @Override
    @OperationLog(value = "新增租户", dataId = "#_res.getData()")
    @Transactional(rollbackFor = Exception.class)
    public R<String> add(TenantDTO tenant) {
        if (this.repo.exists(Wrappers.<TenantDO>lambdaQuery().eq(TenantDO::getCode, tenant.getCode()))) {
            throw new FrameworkException(TenantExceptionInfo.DUPLICATE_TENANT_CODE);
        }
        if (this.repo.exists(Wrappers.<TenantDO>lambdaQuery().eq(TenantDO::getName, tenant.getName()))) {
            throw new FrameworkException(TenantExceptionInfo.DUPLICATE_TENANT_NAME);
        }
        // 将租户信息转换为租户实体对象
        TenantDO entity = Tools.Bean.copy(tenant, TenantDO.class);
        // 生成唯一ID
        entity.setId(Tools.Id.uuid());
        // 设置排序顺序
        entity.setSortOrder(this.getMaxSortOrder() + 1);
        // 检查状态和周期
        this.checkStatusAndPeriod(entity);
        // 保存租户实体对象
        this.repo.save(entity);
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                // 发布租户初始化事件
                applicationContext.publishEvent(new TenantInitEvent(this, entity.getCode(), entity.getName()));
            }
        });
        // 返回添加结果
        return R.ok(entity.getId());
    }

    /**
     * 删除操作
     *
     * @param code 删除的代码
     * @return 删除结果
     */
    @Override
    @OperationLog(value = "删除租户")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> delete(String code) {
        // 删除操作
        this.repo.remove(Wrappers.<TenantDO>query().lambda().eq(TenantDO::getCode, code));
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                // 发布事件
                applicationContext.publishEvent(new TenantDeleteEvent(this, code));
            }
        });

        // 返回删除成功结果
        return R.ok();
    }

    /**
     * 更新租户信息
     *
     * @param tenant 租户信息
     * @return 更新结果
     */
    @Override
    @OperationLog(value = "更新租户")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> update(TenantDTO tenant) {
        if (this.repo.exists(Wrappers.<TenantDO>lambdaQuery().ne(TenantDO::getId, tenant.getId()).eq(TenantDO::getCode, tenant.getCode()))) {
            throw new FrameworkException(TenantExceptionInfo.DUPLICATE_TENANT_CODE);
        }
        if (this.repo.exists(Wrappers.<TenantDO>lambdaQuery().ne(TenantDO::getId, tenant.getId()).eq(TenantDO::getName, tenant.getName()))) {
            throw new FrameworkException(TenantExceptionInfo.DUPLICATE_TENANT_NAME);
        }
        // 检查租户是否存在
        if (!this.repo.exists(Wrappers.<TenantDO>lambdaQuery().eq(TenantDO::getId, tenant.getId()))) {
            throw new FrameworkException(TenantExceptionInfo.TENANT_NOT_FOUND);
        }
        // 复制租户信息到实体对象
        TenantDO entity = Tools.Bean.copy(tenant, TenantDO.class);
        // 检查实体对象的状态和周期
        this.checkStatusAndPeriod(entity);
        // 更新实体对象到数据库
        this.repo.updateById(entity);
        // 返回更新结果
        return R.ok();
    }

    /**
     * 分页查询租户信息
     *
     * @param queryParam 查询参数
     * @return 分页结果
     */
    @Override
    public R<List<TenantDTO>> page(TenantQueryParamDTO queryParam) {
        return R.ok(PageResult.build(this.repo.page(new Page<>(queryParam.getPage(), queryParam.getRows()), TenantRepository.createWrapper(queryParam))).convertBatch(this::modelConvert));
    }

    /**
     * 获取租户详情
     *
     * @param id 租户ID
     * @return 租户详情
     */
    @Override
    public R<TenantDTO> detail(String id) {
        // 根据ID获取租户实体
        TenantDO entity = this.repo.getById(id);
        // 如果实体为空，则抛出租户未找到异常
        if (null == entity) {
            throw new FrameworkException(TenantExceptionInfo.TENANT_NOT_FOUND);
        }
        // 将实体转换为租户DTO并返回
        return R.ok(this.modelConvert(entity));
    }

    /**
     * 根据代码获取租户信息
     *
     * @param code 租户代码
     * @return 租户信息
     */
    @Override
    public R<TenantDTO> getByCode(String code) {
        // 通过代码查询租户实体
        TenantDO entity = this.repo.getOne(Wrappers.<TenantDO>lambdaQuery().eq(TenantDO::getCode, code));
        // 如果查询不到租户实体，则抛出异常
        if (null == entity) {
            throw new FrameworkException(TenantExceptionInfo.TENANT_NOT_FOUND);
        }
        // 将租户实体转换为租户DTO并返回
        return R.ok(this.modelConvert(entity));
    }

    /**
     * 根据当前登录用户获取租户信息
     *
     * @return 租户信息
     */
    @Override
    public R<TenantDTO> getByLoginUser() {
        // 通过租户代码查询租户实体
        TenantDO entity = this.repo.getOne(Wrappers.<TenantDO>lambdaQuery().eq(TenantDO::getCode, Me.tenantCode()));
        // 如果查询结果为空，则抛出租户未找到异常
        if (null == entity) {
            throw new FrameworkException(TenantExceptionInfo.TENANT_NOT_FOUND);
        }
        // 将租户实体转换为租户DTO对象，并返回
        return R.ok(this.modelConvert(entity));
    }

    /**
     * 根据当前登录用户获取租户列表
     *
     * @return 租户列表
     */
    @Override
    public R<List<TenantDTO>> listByLoginUser() {
        // 获取当前登录用户的所有代码
        List<String> codes = this.tenantUserApi.loadCodeByUserId(Me.id());
        // 如果代码列表为空，则返回空列表
        if (Tools.Coll.isBlank(codes)) {
            if (Me.isAdmin()) {
                TenantDTO tenant = new TenantDTO();
                tenant.setStatus(TenantStatusEnum.NORMAL);
                return R.ok(Tools.Coll.newList(tenant));
            } else {
                return R.ok(new ArrayList<>());
            }
        }
        // 根据代码列表查询租户列表，并转换为租户DTO列表
        return R.ok(this.modelConvert(this.repo.list(Wrappers.<TenantDO>lambdaQuery().in(TenantDO::getCode, codes))));
    }

    /**
     * 重试方法
     * @param tenantId 租户ID
     * @return 返回结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Object> retry(String tenantId) {
        // 获取租户实体
        TenantDO entity = this.repo.getById(tenantId);
        if (entity == null) {
            throw new FrameworkException(TenantExceptionInfo.TENANT_NOT_FOUND);
        }
        // 注册事务同步
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                // 发布租户初始化事件
                applicationContext.publishEvent(new TenantInitEvent(this, entity.getCode(), entity.getName()));
            }
        });
        return R.ok();
    }

    /**
     * 每天凌晨0点执行的检查状态任务
     */
    @Scheduled(cron = "0 0 0 * * ?")
    @Transactional(rollbackFor = Exception.class)
    public void checkStatus() {
        List<TenantDO> entities = this.repo.list();
        if (Tools.Coll.isBlank(entities)) {
            return;
        }
        this.repo.updateBatchById(Tools.Coll.traverse(entities, entity -> {
            // 如果租户结束日期不为空且当前日期晚于租户结束日期，则将租户状态设置为冻结
            if (null != entity.getEndDate() && LocalDate.now().isAfter(entity.getEndDate())) {
                entity.setStatus(TenantStatusEnum.FREEZE.name());
            }
            // 如果租户开始日期不为空且当前日期早于租户开始日期，则将租户状态设置为正常
            if (null != entity.getStartDate() && !LocalDate.now().isBefore(entity.getStartDate())) {
                entity.setStatus(TenantStatusEnum.NORMAL.name());
            }
            return entity;
        }));
    }

    /**
     * 注册系统初始化事件监听器
     */
    @EventListener(SystemInitEvent.class)
    @Transactional(rollbackFor = Exception.class)
    public void systemInitEventListener() {
        // 如果默认租户已存在，则直接返回
        if (this.repo.exists(Wrappers.<TenantDO>lambdaQuery().eq(TenantDO::getCode, DEFAULT_TENANT_CODE))) {
            return;
        }
        // 创建新的租户实体
        TenantDO entity = new TenantDO();
        entity.setId(Tools.Id.uuid());
        entity.setCode(DEFAULT_TENANT_CODE);
        entity.setName(DEFAULT_TENANT_NAME);
        entity.setType(TenantTypeEnum.NORMAL.name());
        entity.setStatus(TenantStatusEnum.NORMAL.name());
        entity.setSortOrder(1);
        // 保存租户实体
        this.repo.save(entity);
        // 注册事务同步
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                // 发布租户初始化事件
                applicationContext.publishEvent(new TenantInitEvent(this, entity.getCode(), entity.getName()));
            }
        });
    }


    /**
     * 应用发布事件监听器
     */
    @EventListener
    @Transactional(rollbackFor = Exception.class)
    public void applicationPublishEventListener(ApplicationPublishEvent event) {
        // 获取所有租户实体
        List<String> tenantCodes = this.repo.listObjs(Wrappers.<TenantDO>lambdaQuery().select(TenantDO::getCode));
        // 如果租户实体列表不为空
        if (Tools.Coll.isNotBlank(tenantCodes)) {
            // 发布应用菜单初始化事件
            this.applicationContext.publishEvent(new DevApplicationMenuInitEvent(this, tenantCodes, event.getAppCode()));
        }
    }

    /**
     * 将TenantDO对象转换为TenantDTO对象
     *
     * @param entity 要转换的对象
     * @return 转换后的TenantDTO对象
     */
    private TenantDTO modelConvert(TenantDO entity) {
        TenantDTO tenant = Tools.Bean.copy(entity, TenantDTO.class);
        if (null != tenant.getType()) {
            tenant.setTypeName(tenant.getType().description);
        }
        if (null != tenant.getStatus()) {
            tenant.setStatusName(tenant.getStatus().description);
        }
        return tenant;
    }

    /**
     * 将实体列表转换为DTO列表
     *
     * @param entities 实体列表
     * @return DTO列表
     */
    private List<TenantDTO> modelConvert(List<TenantDO> entities) {
        return Tools.Coll.convertList(entities, this::modelConvert);
    }

    /**
     * 检查租户状态和有效期
     * @param entity 租户实体
     */
    private void checkStatusAndPeriod(TenantDO entity) {
        // 当前状态正常，指定了开始时间
        // 开始时间晚于当前时间，当前状态不能为正常
        if (TenantStatusEnum.NORMAL.name().equals(entity.getStatus()) &&
                null != entity.getStartDate() &&
                entity.getStartDate().isAfter(LocalDate.now())) {
            throw new FrameworkException(TenantExceptionInfo.INVALID_START_DATE_WITH_NORMAL_STATUS);
        }
        // 当前状态正常，指定了结束时间
        // 结束时间早于当前时间，当前状态不能为正常
        if (TenantStatusEnum.NORMAL.name().equals(entity.getStatus()) &&
                null != entity.getEndDate() &&
                entity.getEndDate().isBefore(LocalDate.now())) {
            throw new FrameworkException(TenantExceptionInfo.INVALID_END_DATE_WITH_NORMAL_STATUS);
        }
        // 当前状态冻结，指定了开始时间和结束时间
        // 开始时间早于或等于当前时间，结束时间晚于或等于当前时间，当前状态不能为冻结
        if (TenantStatusEnum.FREEZE.name().equals(entity.getStatus()) &&
                null != entity.getStartDate() &&
                null != entity.getEndDate() &&
                !entity.getStartDate().isAfter(LocalDate.now())) {
            throw new FrameworkException(TenantExceptionInfo.INVALID_END_DATE_WITH_NORMAL_STATUS);
        }
    }

    /**
     * 获取最大排序顺序
     *
     * @return 最大排序顺序
     */
    private Integer getMaxSortOrder() {
        // 查询排序顺序列表
        List<Number> orders = this.repo.listObjs(Wrappers.<TenantDO>lambdaQuery().select(TenantDO::getSortOrder).orderByDesc(TenantDO::getSortOrder));
        // 如果排序顺序列表为空，则返回0，否则返回第一个排序顺序的整数值
        return Tools.Coll.isBlank(orders) ? 0 : orders.get(0).intValue();
    }
}
